/**
 *  Copyright (C) 2008-2013  Telosys project org. ( http://www.telosys.org/ )
 *
 *  Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *          http://www.gnu.org/licenses/lgpl.html
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.telosys.persistence.impl;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

import org.telosys.common.data.BeanList;
import org.telosys.common.dataset.DataSet;
import org.telosys.persistence.PersistenceManager;
import org.telosys.persistence.PersistenceTransaction;
import org.telosys.persistence.Query;
import org.telosys.persistence.TelosysPersistenceException;
import org.telosys.persistence.dao.EntityDAO;
import org.telosys.persistence.dao.Provider;
import org.telosys.persistence.dao.SqlRequests;
import org.telosys.persistence.database.DatabaseInfo;
import org.telosys.persistence.database.DatabaseManager;
import org.telosys.persistence.dataset.DataSetSqlLoader;
import org.telosys.persistence.dbfeatures.DbFeatures;
import org.telosys.persistence.dbfeatures.SpecificSqlOperations;
import org.telosys.persistence.env.SqlQueryContainer;
import org.telosys.persistence.sql.Criteria;
import org.telosys.persistence.sql.SqlQuery;
import org.telosys.util.RollingCounter;

/**
 * Lightweight persistence API implementation for JDBC/SQL
 * 
 * @author Laurent Guerin
 *
 */
public class PersistenceManagerImpl implements PersistenceManager
{
	private final static int ACTION_LOAD          = 1 ;
	private final static int ACTION_INSERT        = 2 ;
	private final static int ACTION_UPDATE        = 3 ;
	private final static int ACTION_DELETE        = 4 ;
	private final static int ACTION_SAVE          = 5 ;
	private final static int ACTION_EXISTS        = 6 ;
	private final static int ACTION_INSERT_KEYGEN = 7 ;
	//private final static int ACTION_LOAD_LINK     = 8 ;

	private final static int ACTION_INSERT_LIST = 101 ;
	private final static int ACTION_UPDATE_LIST = 102 ;
	private final static int ACTION_DELETE_LIST = 103 ;
	
	private final static int ACTION_DELETE_QUERY = 201 ;
	private final static int ACTION_COUNT_QUERY  = 202 ;
	private final static int ACTION_LOAD_QUERY   = 203 ;
	
	private final static RollingCounter instanceCounter = new RollingCounter();
	
	private final int               instanceId ;
	
	private final int               databaseId ;
	
	private Connection              connection = null;
	
	private PersistenceTransaction  transaction = null ;
	
	private final boolean           bMemoAutoCommitMode ; //= false;	
	
	private Provider                provider   = null ;
	
    private boolean                 open       = false;
	
	private SqlQueryContainer       sqlQueryContainer = null ;
	
	private SpecificSqlOperations   specificSqlOperations  = null ;

	protected final static int TYPE_STANDARD  = 0 ;
	protected final static int TYPE_TEMPORARY = 1 ;  // For temporary usage in Proxy
	
	private int                     type = TYPE_STANDARD ;
	
	/**
	 * Set the type : TEMPORARY or STANDARD 
	 * For framework internal use only
	 * @param type
	 */
	void setType(int type) {
		this.type = type ;
	}
	
	/**
	 * Get the type : TEMPORARY or STANDARD 
	 * For framework internal use only
	 */
	int getType() {
		return this.type ;
	}
	
	/**
	 * Constructor 
	 * @param databaseId
	 * @param connection
	 * @param provider
	 * @param sqlQueryContainer
	 * @param specificSqlOperations
	 */
	protected PersistenceManagerImpl(int databaseId, 
			Connection connection, Provider provider, 
			SqlQueryContainer sqlQueryContainer, SpecificSqlOperations specificSqlOperations ) 
	{
		super();
		
		this.instanceId = instanceCounter.increment() ;
		
		//--- Store the original AutoCommit mode
		bMemoAutoCommitMode = getAutoCommitMode(connection);
		
		this.connection  = connection ;
		try {
			this.connection.setAutoCommit(true);
		} catch (SQLException e) {
			throw new TelosysPersistenceException("Cannot set connection in AUTOCOMMIT (SQLException)", e);
		}
		
		this.databaseId  = databaseId ;
		this.transaction = new PersistenceTransactionImpl(connection);

		this.sqlQueryContainer = sqlQueryContainer ;
				
		this.provider = provider ;
		//this.sequenceDAO = sequenceDAO ;
		this.specificSqlOperations = specificSqlOperations ;
		
		this.open = true ;
	}
	
	// @Override
	public int getInstanceId()
	{
		return this.instanceId ;
	}
	
	// @Override
	public void close()
	{
		this.open = false ;
		
		try {
			//--- Restore the original AutoCommit mode
			setAutoCommitMode(connection, bMemoAutoCommitMode);
			//--- Close the connection
			connection.close();
		} catch (SQLException e) {
			throw new TelosysPersistenceException("Cannot close connection (SQLException)", e);
		} finally {
			this.connection  = null ;
			this.transaction = null ;
			this.provider    = null ;
		}
	}

	// @Override
	public boolean isOpen()
	{
		return this.open ;
	}
	
	private void checkNotClosed()
	{
		if ( this.open != true ) {
			throw new TelosysPersistenceException("The manager is closed.");
		}
	}
	
	// @Override
	public boolean isAutoCommitMode()
	{
		checkNotClosed();
		if ( this.connection != null ) {
			boolean b = false ;
			try {
				b = connection.getAutoCommit();
			} catch (SQLException e) {
				throw new TelosysPersistenceException("Cannot get AutoCommit mode from connection (SQLException)", e);
			}
			return b;
		}
		else {
			throw new TelosysPersistenceException("Cannot get AutoCommit mode (connection is null)");
		}
	}
	
	public DatabaseInfo getDatabaseInfo() 
	{
		return DatabaseManager.getDatabaseInfo( this.databaseId ) ;
	}
	
	// @Override
	public <T> SqlRequests<T> getSqlRequests(Class<T> entityClass)
	{
		checkNotClosed();
		EntityDAO<T> entityDAO = provider.getDAO(entityClass);
		return entityDAO.getSqlRequests();
	}
	
	public <T> EntityInfoImpl getEntityInfo(Class<T> entityClass)
	{
		checkNotClosed();
		EntityDAO<T> entityDAO = provider.getDAO(entityClass);
		return entityDAO.getEntityInfo();
	}
	
	// @Override
	public PersistenceTransaction getTransaction() 
	{
    	if ( this.transaction != null ) {
        	return this.transaction ;
    	}
    	else {
			//--- Attempting to use after "close" 
			throw new IllegalStateException("This manager is closed");    		
    	}
	}
    //---------------------------------------------------------------------------    
    private Connection getConnectionToUse() 
    {
    	if ( this.connection != null ) {
        	return this.connection ;
    	}
    	else {
			//--- Attempting to use after "close" 
			throw new IllegalStateException("This manager is closed");    		
    	}
    }

    //---------------------------------------------------------------------------
    private void releaseConnectionIfTemporary(Connection con)
    {
    	// Nothing to do : always the same connection
    }
    
    //---------------------------------------------------------------------------
    // "AutoCommit" management
    //---------------------------------------------------------------------------
    /**
     * Returns the current auto-commit mode of the given connection
     * 
     * @param con
     * @return
     */
    private boolean getAutoCommitMode(Connection con)
    {
        try
        {
            return con.getAutoCommit();
        } catch (SQLException ex)
        {
			throw new TelosysPersistenceException("Cannot get connection AutoCommit mode (SQLException)", ex);
        }
    }

    //---------------------------------------------------------------------------
    /**
     * Sets the auto-commit mode of the given connection
     * 
     * @param con
     * @param bMode
     */
    private void setAutoCommitMode(Connection con, boolean bMode)
    {
        try
        {
            con.setAutoCommit(bMode);
        } catch (SQLException ex)
        {
			throw new TelosysPersistenceException("Cannot set connection AutoCommit mode (SQLException)", ex);
        }
    }
	
//	private UntypedDAO getUntypedDAO(Class<?> beanClass) 
//	{
//		checkNotClosed();
//
//		if ( beanClass != null ) {
//			
//			EntityDAO<?> entityDAO = provider.getDAO(beanClass);
//		    //--- Create a generic DAO adapter 
//		    UntypedDAO dao = new UntypedDAO(entityDAO);
//			return dao ;
//		}
//		else {
//			throw new TelosysPersistenceException("cannot get DAO (bean class is null)");
//		}
//	}

	private <T> EntityDAO<T> getEntityDAO(Class<T> beanClass) 
	{
		checkNotClosed();

		if ( beanClass != null ) {
			EntityDAO<T> entityDAO = provider.getDAO(beanClass);
			return entityDAO ;
		}
		else {
			throw new TelosysPersistenceException("cannot get DAO (bean class is null)");
		}
	}
	
	@SuppressWarnings("unchecked")
	private EntityDAO<Object> getObjectDAO(Class<?> beanClass) 
	{
		EntityDAO<?> entityDAO = getEntityDAO(beanClass); 
		return (EntityDAO<Object>) entityDAO ;
	}
	
	@SuppressWarnings("unchecked")
	private List<Object> castToListOfObjects( List<?> list ) {
		return (List<Object>) list ;
	}
	
	@SuppressWarnings("unchecked")
	private Query<Object> castToQueryForObject( Query<?> query ) {
		return (Query<Object>) query ;
	}
	

    //-------------------------------------------------------------------------------------------------------------------
    // LOAD
    //-------------------------------------------------------------------------------------------------------------------
	/**
	 * Try to load the given entity <br>
	 * The Primary Key attribute(s) of the entity must be set before calling this method<br>
	 * If found the entity attributes are set, if not found the entity is not modified <br>
	 * @param entity the entity instance to load 
	 * @return true if found and loaded, false if not found
	 */
	// @Override
	public boolean load(Object entity)
	{
		long r = processEntityAction( entity, ACTION_LOAD );
		return ( r != 0 ) ;
	}
	
	// @Override
    public boolean loadForUpdate(Object entity) 
    {
		long r = processEntityAction( entity, ACTION_LOAD, getForUpdateString() );
		return ( r != 0 ) ;
    }

	// @Override
    public <T> T loadByKey(Class<T> entityClass, Object ... keyValues ) 
    {
    	//UntypedDAO dao = getUntypedDAO(entityClass) ;
    	EntityDAO<T> dao = getEntityDAO(entityClass) ;
		Connection connectionToUse = getConnectionToUse() ;
		T entity = null ;
		try {
			//entity = dao.<T>loadByKey(connectionToUse, keyValues);
			entity = dao.loadByKey(connectionToUse, keyValues);
		}
		finally {
			releaseConnectionIfTemporary(connectionToUse);
		}
		return entity ;
    }

    public <T> T loadByKeyForUpdate(Class<T> entityClass, Object ... keyValues ) 
    {
    	//UntypedDAO dao = getUntypedDAO(entityClass) ;
    	EntityDAO<T> dao = getEntityDAO(entityClass) ;
		Connection connectionToUse = getConnectionToUse() ;
		String forUpdateString = getForUpdateString() ;
		T entity = null ;
		try {
			//entity = dao.<T>loadByKeyForUpdate(connectionToUse, forUpdateString, keyValues);
			entity = dao.loadByKeyForUpdate(connectionToUse, forUpdateString, keyValues);
		}
		finally {
			releaseConnectionIfTemporary(connectionToUse);
		}
		return entity ;
    }

    private String getForUpdateString() 
    {
    	String forUpdate = DbFeatures.DEFAULT_FOR_UPDATE_STRING ;
    	
    	if ( specificSqlOperations != null ) {
    		forUpdate = specificSqlOperations.getForUpdateString() ;
    		if ( null == forUpdate ) {
    			// never happens 
    			throw new TelosysPersistenceException("'FOR UPDATE' not supported by this DB");
    		}
    	}
		return forUpdate ;
    }
    
    //-------------------------------------------------------------------------------------------------------------------
    // LOAD LINK "TO ONE"
    //-------------------------------------------------------------------------------------------------------------------
    /* (non-Javadoc)
     * @see org.telosys.persistence.PersistenceManager#loadLink(java.lang.Class, java.lang.Object)
     */
    public <T> T loadLink(Class<T> referencedObjectClass, Object entity ) 
    {
    	//UntypedDAO dao = getUntypedDAO(entity.getClass()) ;
    	EntityDAO<?> dao = getEntityDAO(entity.getClass()) ;
    	Object[] keyParts = dao.getLinkToOneKeyParts(entity, referencedObjectClass);
    	return loadLink(referencedObjectClass, keyParts) ;
    }

    /* (non-Javadoc)
     * @see org.telosys.persistence.PersistenceManager#loadLink(java.lang.Class, java.lang.Object, java.lang.String)
     */
    public <T> T loadLink(Class<T> referencedObjectClass, Object entity, String linkName ) {
    	//UntypedDAO dao = getUntypedDAO(entity.getClass()) ;
    	EntityDAO<?> dao = getEntityDAO(entity.getClass()) ;
    	Object[] keyParts = dao.getLinkToOneKeyParts(entity, referencedObjectClass, linkName);
    	return loadLink(referencedObjectClass, keyParts) ;
    }
    
    private <T> T loadLink(Class<T> referencedObjectClass, Object[] keyParts) 
    {
		// check at least one key part not null 
		boolean atLeastOneKeyPartNotNull = false ;
		for ( Object o : keyParts ) {
			if ( o != null ) {
				atLeastOneKeyPartNotNull = true ;
			}
		}
		
		if ( atLeastOneKeyPartNotNull ) {
	    	//--- Try to load the link object using its own DAO
	    	T linkObject = loadByKey(referencedObjectClass, keyParts );
			return linkObject ;
		}
		else {
			// No FK to reference the link  
			return null ;
		}    	
    }

    //-------------------------------------------------------------------------------------------------------------------
    // LOAD LINK "TO MANY"
    //-------------------------------------------------------------------------------------------------------------------
    public <T> List<T> loadLinks(Class<T> referencedObjectClass, Object entity ) {
    	//UntypedDAO dao = getUntypedDAO(entity.getClass()) ;
    	EntityDAO<?> dao = getEntityDAO(entity.getClass()) ;
    	Criteria criteria = dao.getLinkToManyCriteria(entity, referencedObjectClass);
    	return loadLinksByQuery(referencedObjectClass, criteria) ;
    }
    
    public <T> List<T> loadLinks(Class<T> referencedObjectClass, Object entity, String linkName ) {
    	//UntypedDAO dao = getUntypedDAO(entity.getClass()) ;
    	EntityDAO<?> dao = getEntityDAO(entity.getClass()) ;
    	Criteria criteria = dao.getLinkToManyCriteria(entity, referencedObjectClass, linkName);
    	return loadLinksByQuery(referencedObjectClass, criteria) ;
    }
    
    private <T> List<T> loadLinksByQuery(Class<T> referencedObjectClass, Criteria criteria ) {
    	Query<T> query = createQuery(referencedObjectClass, criteria);
    	List<T> result = loadByQuery(query);
    	return result ;
    }
    
    //-------------------------------------------------------------------------------------------------------------------
    // SAVE
    //-------------------------------------------------------------------------------------------------------------------
	// @Override
	public void save(Object entity) 
	{
		processEntityAction( entity, ACTION_SAVE );
	}

    //-------------------------------------------------------------------------------------------------------------------
    // DELETE
    //-------------------------------------------------------------------------------------------------------------------
	// @Override
	public boolean delete(Object entity) 
	{
		long ret = processEntityAction( entity, ACTION_DELETE );
		return ( ret > 0 ) ; 
	}
	
    //-------------------------------------------------------------------------------------------------------------------
    // UPDATE
    //-------------------------------------------------------------------------------------------------------------------
	// @Override
	public boolean update(Object entity) 
	{
		long ret = processEntityAction( entity, ACTION_UPDATE );
		return ( ret > 0 ) ; 
	}
	
    //-------------------------------------------------------------------------------------------------------------------
    // INSERT
    //-------------------------------------------------------------------------------------------------------------------
	/**
	 * Inserts the given entity in the table <br>
	 * If the Primary Key already exists (and is not an auto-incremented key) an Exception is thrown <br>
	 * If the Primary Key is auto-incremented, the insert always creates a new record in the table<br>
	 * and the key attribute of the entity is set with the generated key.
	 * @param entity
	 */
	// @Override
	public void insert(Object entity) 
	{
		processEntityAction( entity, ACTION_INSERT );
	}

	// @Override
	public long insertKeyGen(Object entity) 
	{
		return processEntityAction( entity, ACTION_INSERT_KEYGEN );
	}
	
    //-------------------------------------------------------------------------------------------------------------------
    // EXISTS
    //-------------------------------------------------------------------------------------------------------------------
	// @Override
	public boolean exists(Object entity) 
	{
		long r = processEntityAction( entity, ACTION_EXISTS );
		return ( r != 0 ) ;
	}
	
    //-------------------------------------------------------------------------------------------------------------------
	// @Override
    //public <T> List<T> loadByQuery( Query query, Class<T> entityClass ) 
    public <T> List<T> loadByQuery( Query<T> query ) 
    {
		//BeanList<T> list = new BeanList<T>( entityClass );
		BeanList<T> list = query.createList();
		processQuery( query, ACTION_LOAD_QUERY, list );
		return list ;
    }
    
	// @Override
    public <T> int loadByQuery( Query<T> query, List<T> list )
    {
		return processQuery( query, ACTION_LOAD_QUERY, list );
    }
    
    /**
     * Load all the entities of a table
     * @param entityClass
     * @return a LinkedList containing all the entities
     */
	// @Override
    public <T> List<T> loadAll( Class<T> entityClass ) 
    {
        return loadAll(entityClass, null );
    }
    
    /**
     * Load all the entities of a table with an "order by" clause
     * @param entityClass
     * @param orderBy the SQL "order by ... " string
     * @return
     */
	// @Override
    public <T> List<T> loadAll( Class<T> entityClass, String orderBy ) 
    {
    	Query<T> query ;
    	if ( orderBy != null ) {
    		query = createQueryAll(entityClass, orderBy);
    	}
    	else {
    		query = createQueryAll(entityClass);
    	}
    	
        //List<T> list = new LinkedList<T>();
        List<T> list = new BeanList<T>(entityClass);
        
        //load(list, query);
        loadByQuery(query, list);
        
        return list ;
    }
    
    //-------------------------------------------------------------------------------------------------------------------
    // INSERT LIST
    //-------------------------------------------------------------------------------------------------------------------
	// @Override
    public int insert(List<? extends Object> list ) 
    {
    	return processListAction(list, null, ACTION_INSERT_LIST);
    }

	// @Override
    public int insert(List<? extends Object> list, Class<?> entityClass) 
    {
    	return processListAction(list, entityClass, ACTION_INSERT_LIST);
    }

    //-------------------------------------------------------------------------------------------------------------------
    // UPDATE LIST
    //-------------------------------------------------------------------------------------------------------------------
	// @Override
    public int update(List<? extends Object> list ) 
    {
    	return processListAction(list, null, ACTION_UPDATE_LIST);
    }
    
	// @Override
    public int update(List<? extends Object> list, Class<?> entityClass ) 
    {
    	return processListAction(list, entityClass, ACTION_UPDATE_LIST);
    }

    //-------------------------------------------------------------------------------------------------------------------
    // SAVE LIST
    //-------------------------------------------------------------------------------------------------------------------
// TODO : implement save(list) ????
//    public int save(List<? extends Object> list, Class<?> entityClass ) throws TelosysException
//    {
//		StandardDAOAdapter dao = getDAO(entityClass) ;
//		if ( connection != null ) {
//			return dao.saveList(list, connection);
//		}
//		else {
//			return dao.saveList(list);
//		}
//    }

    //-------------------------------------------------------------------------------------------------------------------
    // DELETE LIST
    //-------------------------------------------------------------------------------------------------------------------
	// @Override
    public int delete(List<? extends Object> list ) 
    {
    	return processListAction(list, null, ACTION_DELETE_LIST);
    }

    // @Override
    public int delete(List<? extends Object> list, Class<?> entityClass ) 
    {
    	return processListAction(list, entityClass, ACTION_DELETE_LIST);
    }
	
    /**
     * Returns the class of the first element found in the list
     * @param list
     * @return
     */
    private Class<?> getElementClass(List<? extends Object> list ) 
    {
    	for ( Object o : list ) {
    		if ( o != null ) {
    			return o.getClass();
    		}
    	}
    	return null ;
    }
    
    private int processListAction(List<? extends Object> list, Class<?> entityClass, int action ) 
    {
    	if ( null == list ) {
    		return 0 ;
    	}
    	if ( list.size() > 0 ) {
    		//UntypedDAO dao = null ;
    		EntityDAO<Object> dao = null ;
    		if ( entityClass != null ) {
        		//dao = getUntypedDAO(entityClass) ;
        		dao = getObjectDAO(entityClass);
    		}
    		else {
    			// Try to infer the list type
    			Class<?> elementClass = getElementClass(list);
    			if ( elementClass != null ) {
            		//dao = getUntypedDAO( elementClass ) ;
            		dao = getObjectDAO(elementClass);
    			}
    			else {
    				return 0 ; // List of null elements => nothing to process
    			}
    		}
    		
    		Connection connectionToUse = getConnectionToUse() ;
        	//List<Object>  untypedList  = (List<Object>)list ;
        	List<Object>  untypedList  = castToListOfObjects( list );


    		int r = 0 ;
    		try {
	    		switch(action) 
	    		{
	    		case ACTION_INSERT_LIST :
	    			r = dao.insertList(untypedList, connectionToUse);
	    			break;
	    		case ACTION_DELETE_LIST :
	    			r = dao.deleteList(untypedList, connectionToUse);
	    			break;
	    		case ACTION_UPDATE_LIST :
	    			r = dao.updateList(untypedList, connectionToUse);
	    			break;
	    		default :
	    			//throw new TelosysException("Internal error : invalid action value " + action );
	    			throw new TelosysPersistenceException("Internal error : invalid action value " + action );
	    		}
    		}
    		finally {
    			releaseConnectionIfTemporary(connectionToUse);
    		}
    		return r;
    	}
    	else {
        	return 0 ;
    	}
    }
    
    private long processEntityAction( Object entity, int action ) 
    {
    	return processEntityAction( entity, action, null );
    }
    
    private long processEntityAction( Object entity, int action, String optionalClause ) 
    {
    	// , String optionalClause
    	if ( null == entity ) {
    		throw new IllegalArgumentException("Record object is null");
    	}
    	
    	//UntypedDAO dao = getUntypedDAO( entity.getClass() ) ;
    	//EntityDAO<Object> dao = getEntityDAO( entity.getClass() ) ;
    	EntityDAO<Object> dao = getObjectDAO(entity.getClass());
    	
		Connection connectionToUse = getConnectionToUse() ;
		
		long r = 0 ;
		try {
    		switch(action) 
    		{
    		case ACTION_LOAD :
    			boolean b = false ;
    			if ( optionalClause != null ) {
        			b = dao.load(entity, optionalClause, connectionToUse);
    			}
    			else {
        			b = dao.load(entity, connectionToUse);
    			}
    			r = ( b ? 1 : 0 ) ; // 1 for TRUE, 0 for FALSE
    			break;

    		case ACTION_SAVE :
    			r = dao.save(entity, connectionToUse);
    			break;
    		case ACTION_INSERT :
    			r = dao.insert(entity, connectionToUse);
    			break;
    		case ACTION_INSERT_KEYGEN :
    			r = dao.insertKeyGen(entity, connectionToUse);
    			break;
    		case ACTION_UPDATE :
    			r = dao.update(entity, connectionToUse);
    			break;
    		case ACTION_DELETE :
    			r = dao.delete(entity, connectionToUse);
    			break;
    		case ACTION_EXISTS :
    			r = ( dao.exists(entity, connectionToUse) ? 1 : 0 ) ; // 1 for TRUE, 0 for FALSE
    			break;
    		default :
    			throw new TelosysPersistenceException("Internal error : invalid action value " + action );
    		}
		}
//		catch ( TelosysException e) {
//			throw new TelosysPersistenceException( e );
//		}
		finally {
			releaseConnectionIfTemporary(connectionToUse);
		}
		return r;
    }
    
    private int processQuery( Query<?> query, int action, List<?> listData ) 
    {
    	if ( null == query ) {
    		throw new IllegalArgumentException("Query object is null");
    	}
    	
    	if ( query.allParametersInitialized() != true ) {
    		throw new TelosysPersistenceException("Query not ready (all the parameters are not initialized)");
    	}
    		
		//UntypedDAO dao = getUntypedDAO(query.getEntityBeanClass()) ;
    	EntityDAO<Object> dao = getObjectDAO(query.getEntityBeanClass());
    	// Query<Object> untypedQuery = (Query<Object>)query ;
    	Query<Object> untypedQuery = castToQueryForObject( query ) ; 
    	//List<Object>  untypedList  = (List<Object>)listData ;
    	List<Object>  untypedList  = castToListOfObjects( listData );
    			
		Connection connectionToUse = getConnectionToUse() ;
		
		int r = 0 ;
		try {
    		switch(action) 
    		{
    		case ACTION_DELETE_QUERY :
    			r = dao.deleteByQuery(untypedQuery, connectionToUse);
    			break;
    			
    		case ACTION_COUNT_QUERY :
    			r = dao.count(untypedQuery, connectionToUse);
    			break;
    			
    		case ACTION_LOAD_QUERY :
    			r = dao.loadList(untypedList, untypedQuery, connectionToUse);
    			break;
    			
    		default :
    			throw new TelosysPersistenceException("Internal error : invalid action value " + action );
    		}
		}
		finally {
			releaseConnectionIfTemporary(connectionToUse);
		}
		return r;
    }

    //-------------------------------------------------------------------------------------------------------------------
    // DELETE BY QUERY
    //-------------------------------------------------------------------------------------------------------------------
	// @Override
    public int delete(Query<?> query) 
    {
		return processQuery( query, ACTION_DELETE_QUERY, null );
    }
	
    //-------------------------------------------------------------------------------------------------------------------
    // COUNT WITH QUERY
    //-------------------------------------------------------------------------------------------------------------------
	// @Override
    public int count(Query<?> query) 
    {
		return processQuery( query, ACTION_COUNT_QUERY, null );
    }
	
	// @Override
    public int countAll(Class<?> entityClass) 
    {
    	Query<?> query = createQueryAll(entityClass);
		return processQuery( query, ACTION_COUNT_QUERY, null );
    }
	

    //-------------------------------------------------------------------------------------------------------------------
    // DATASET
    //-------------------------------------------------------------------------------------------------------------------
    public int countDataSetRows( SqlQuery query) 
    {
		DataSetSqlLoader loader = new DataSetSqlLoader();
		Connection connectionToUse = getConnectionToUse() ;
		int count = loader.count(query, connectionToUse);
		return count ;
    }
    
    public DataSet loadDataSet( SqlQuery query) 
    {
		DataSetSqlLoader loader = new DataSetSqlLoader();
		Connection connectionToUse = getConnectionToUse() ;
		DataSet dataset = new DataSet();
		//boolean ret = loader.load(dataset, query, connectionToUse );
		loader.load(dataset, query, connectionToUse );
		return dataset ;
    }
    
    public void loadDataSet( SqlQuery query, DataSet dataset ) 
    {
		DataSetSqlLoader loader = new DataSetSqlLoader();
		Connection connectionToUse = getConnectionToUse() ;
		loader.load(dataset, query, connectionToUse );
    }
    
    //-------------------------------------------------------------------------------------------------------------------
    // QUERY ALL
    //-------------------------------------------------------------------------------------------------------------------
	// @Override
    public <T> Query<T> createQueryAll( Class<T> entityClass ) 
    {
		EntityDAO<T> dao = getEntityDAO(entityClass) ;
		return dao.createQueryAll();
    }

	// @Override
    public <T> Query<T> createQueryAll( Class<T> entityClass, String sQueryEndOfSelect ) 
    {
		EntityDAO<T> dao = getEntityDAO(entityClass) ;
		return dao.createQueryAll(sQueryEndOfSelect);
    }

    //-------------------------------------------------------------------------------------------------------------------
    // QUERY
    //-------------------------------------------------------------------------------------------------------------------
	// @Override
    public <T> Query<T> createQuery(Class<T> entityClass, String sQueryCriteria) 
    {
		EntityDAO<T> dao = getEntityDAO(entityClass) ;
		return dao.createQuery(sQueryCriteria);
    }

	// @Override
    public <T> Query<T> createQuery(Class<T> entityClass, String sQueryCriteria, String sQueryEndOfSelect) 
    {
		EntityDAO<T> dao = getEntityDAO(entityClass) ;
		return dao.createQuery(sQueryCriteria, sQueryEndOfSelect);
    }

    //-------------------------------------------------------------------------------------------------------------------
    // QUERY WITH CRITERIA 
    //-------------------------------------------------------------------------------------------------------------------
	// @Override
	public <T> Query<T> createQuery(Class<T> entityClass, Criteria criteria)
    {
		EntityDAO<T> dao = getEntityDAO(entityClass) ;
		return dao.createQuery(criteria);
    }

	// @Override
	public <T> Query<T> createQuery(Class<T> entityClass, Criteria criteria, String sQueryEndOfSelect)
    {
		EntityDAO<T> dao = getEntityDAO(entityClass) ;
		return dao.createQuery(criteria, sQueryEndOfSelect);
    }

	
	public SqlQuery getNamedQuery(String name)
    {
		if ( sqlQueryContainer != null ) {
			return sqlQueryContainer.getSqlQuery(name);
		}
		else {
			throw new TelosysPersistenceException("No query container defined.");
		}
    }
	
    //-------------------------------------------------------------------------------------------------------------------
    // SEQUENCE
    //-------------------------------------------------------------------------------------------------------------------
	//@Override
    public long sequenceNextVal( String sequenceName )
    {
    	if ( null == specificSqlOperations ) {
			throw new TelosysPersistenceException("Sequence not supported (no specific operations for this DB)");
    	}
    	return specificSqlOperations.sequenceNextVal(connection, sequenceName);
    }
    
}
